/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package czStvDpds.graphics3D;

import czStvDpds.graphics3D.geometry.*;
import czStvDpds.input.LWJGLKeyEvent;
import czStvDpds.input.LWJGLMouseEvent;
import czStvDpds.logging.Logging;
import czStvDpds.mapmanagment.AccesibleMap;
import czStvDpds.mapmanagment.TilesInfo;
import czStvDpds.movement.AbstractPlayer;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FilenameFilter;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Random;
import java.util.logging.Level;
import org.lwjgl.BufferUtils;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Vector3f;

/**
 * Třida reprezentující herní část grafiky.
 *
 * @author divad
 */
public class GameScene extends Scene {

    private boolean mapGenerated = false;
    private TMDLoader meshLoader;
    private TMMLoader textureLoader;
    private TilesInfo tilesInfo;
    private Object3D[] objects;
    private boolean objectsLoaded;
    private AccesibleMap map;
    private Scene skyScene;
    private ArrayList<GraphicalPlayer> players;
    private ArrayList<GraphicalBullet> bullets;
    ///////////nastavení kamery////////////////
    private AbstractPlayer cameraLocation;
    private boolean freeCam = false;
    private double x = 18, y = 4, z = 20, yaw, pitch;

    public GameScene(int x, int y, int width, int height, float sideRatio, float near, float far, float fov) {
        super(x, y, width, height, sideRatio, near, far, fov, 0.5f, 0.5f, 0, false, false);
        /////////////nový generátor map/////////////////////
        this.tilesInfo = TilesInfo.getTilesInfo();
        this.meshLoader = new TMDLoader();
        this.textureLoader = new TMMLoader();
        this.players = new ArrayList<>();
        this.bullets = new ArrayList<>();

        int maxId = -1;
        int[] ids = this.tilesInfo.getIDs();
        for (int id : ids) {
            maxId = Math.max(maxId, id);
        }
        this.objects = new Object3D[(maxId + 1) * 6];
        skyScene = new Scene(x, y, width, height, sideRatio, near, far, fov, 0.5f, 0.5f, 0, false, false) {

            @Override
            protected void predrawSetup(Scene owner) {
                Matrix4f camPos = getCamPos();
                Matrix4f ownerCamPos = owner.getCamPos();
                camPos.m00 = ownerCamPos.m00;
                camPos.m01 = ownerCamPos.m01;
                camPos.m02 = ownerCamPos.m02;
                camPos.m03 = ownerCamPos.m03;

                camPos.m10 = ownerCamPos.m10;
                camPos.m11 = ownerCamPos.m11;
                camPos.m12 = ownerCamPos.m12;
                camPos.m13 = ownerCamPos.m13;

                camPos.m20 = ownerCamPos.m20;
                camPos.m21 = ownerCamPos.m21;
                camPos.m22 = ownerCamPos.m22;
                camPos.m23 = ownerCamPos.m23;

                camPos.m30 = 0;
                camPos.m31 = 0;
                camPos.m32 = 0;
                camPos.m33 = 1;
            }
        };

        this.sceneSettings.setClearColorOnRepaint(false);
        this.sceneSettings.setSortTransparentObjects(true);

        this.glSettings.setAlphaTest(true);
        this.glSettings.setAlphaFunc(GLSettings.FUNC_GREATER);
        this.glSettings.setAlphaRefValue(0);
        this.glSettings.setBlendSourceFactor(GLSettings.BFACTOR_SRC_ALPHA);
        this.glSettings.setBlendDestinationFactor(GLSettings.BFACTOR_1MIN_SRC_ALPHA);
        this.glSettings.setBlending(true);
    }

    public void init() {
        this.addFirstDrawScene(skyScene, 0);
        this.loadObjects();
    }

    private void loadObjects() {
        int[] ids = this.tilesInfo.getIDs();
        for (int index = 0; index < ids.length; index++) {
            this.loadObject(this.tilesInfo.getName(ids[index]), ids[index]);
        }
        this.objectsLoaded = true;
    }

    private void loadObject(String name, int id) {
        String baseAdress = "resources/models/";
        File meshFolder = new File(baseAdress + "map-" + name);
        if (meshFolder.exists()) {
            File[] meshDatas = meshFolder.listFiles(new FilenameFilter() {

                @Override
                public boolean accept(File dir, String name) {
                    return name.endsWith(".tmd");
                }
            });
            String tileName = this.tilesInfo.getName(id);
            for (File f : meshDatas) {
                try {
                    this.meshLoader.loadFile(f);
                    textureLoader.loadFile(this.meshLoader.getMaterialInfo());
                    Texture[] colorTextures = null;
                    Texture alphaMask = this.textureLoader.getMaskTexture();
                    if (textureLoader.getColorTextures() != null && textureLoader.getColorTextures().length > 0) {
                        colorTextures = textureLoader.getColorTextures();
                    }
                    if (alphaMask != null) {
                        alphaMask.getProperties().setFilterMax(TextureProperties.FILTER_NEAREST);
                        alphaMask.getProperties().setFilterMin(TextureProperties.FILTER_NEAREST);
                    }
                    this.textureLoader.generateAllTextures();

                    Object3D object = new Object3D(new Mesh(this.meshLoader.getMeshData(), this.meshLoader.getIndices(), this.meshLoader.getTexCoordsNumber()));
                    if (alphaMask != null) {
                        object.addTexture(alphaMask, -1);
                    }
                    if (colorTextures != null) {
                        for (Texture t : colorTextures) {
                            object.addTexture(t, -1);
                        }
                    }


                    if (f.getName().endsWith(tileName + ".tmd")) {
                        this.objects[id * 6] = object;
                    } else if (f.getName().endsWith(tileName + "-1.tmd")) {
                        this.objects[id * 6 + 1] = object;
                    } else if (f.getName().endsWith(tileName + "-2a.tmd")) {
                        this.objects[id * 6 + 2] = object;
                    } else if (f.getName().endsWith(tileName + "-2b.tmd")) {
                        this.objects[id * 6 + 3] = object;
                    } else if (f.getName().endsWith(tileName + "-3.tmd")) {
                        this.objects[id * 6 + 4] = object;
                    } else if (f.getName().endsWith(tileName + "-4.tmd")) {
                        this.objects[id * 6 + 5] = object;
                    }
                } catch (Exception ex) {
                    Logging.getMainLogger().log(Level.WARNING, "Nelze nahrát model - " + tileName, ex);
                }
            }
        }
    }

    /**
     * Vygeneruje 3D objekty reprezentující mapu.
     *
     * @param map
     * @see AccesibleMap
     */
    public void generateMap(AccesibleMap map) {
        this.map = map;
        this.mapGenerated = false;
        if (this.objectsLoaded) {
            Random rnd = new Random();
            for (int y = 0; y < map.getHeight(); y++) {
                for (int x = 0; x < map.getWidth(); x++) {
                    Object3D obj = generateMapPart(map, x, y);
                    this.addObject(obj, map.getTile(x, y).getId() == TilesInfo.getTilesInfo().getTile("bpglass").getId());

                    int id = map.getTile(x, y).getId();
                    int bushId = this.tilesInfo.getTile("bush").getId();
                    int treeId = this.tilesInfo.getTile("tree").getId();
                    int barelId = this.tilesInfo.getTile("barel").getId();
                    int boxId = this.tilesInfo.getTile("box").getId();
                    if (id == bushId || id == treeId || id == barelId || id == boxId) {
                        rnd.setSeed(x * x * y * y);
                        obj.getPosMatrix().rotate((float) rnd.nextDouble() * 0.2f, new Vector3f(0, 1, 0));
                        obj.getPosMatrix().translate(new Vector3f((float) (rnd.nextGaussian() * 0.1), 0, (float) (rnd.nextGaussian() * 0.1)));
                    }
                    if (id > 1 && id < 9) {
                        int airId = this.tilesInfo.getTile("air").getId();
                        Object3D obj2 = this.objects[airId * 6];
                        if (obj2 == null) {
                            obj2 = this.generateDefaultObject();
                        } else {
                            obj2 = obj2.copyObjectReference();
                        }
                        obj2.setPosMatrix(new Matrix4f());
                        obj2.getPosMatrix().translate(new Vector3f(x, 0, y));
                        this.addObject(obj2, false);
                    }
                }
            }
            this.mapGenerated = true;
            this.genSkyBox();
            Logging.getMainLogger().info("Graphics map generation complete");
        }
    }

    /**
     * Vrátí načtenou mapu.
     *
     * @return
     */
    public AccesibleMap getMap() {
        return map;
    }

    private void genSkyBox() {

        Object3D skyBox = null;
        this.meshLoader.loadFile(new File("resources/models/skyBox/skyBox.tmd"));
        if (this.meshLoader.getIndices() != null && this.meshLoader.getMeshData() != null) {
            skyBox = new Object3D(new Mesh(this.meshLoader.getMeshData(), this.meshLoader.getIndices(), this.meshLoader.getTexCoordsNumber()));
            if (this.meshLoader.hasMaterialInfo()) {
                this.textureLoader.loadFile(this.meshLoader.getMaterialInfo());
                for (Texture t : this.textureLoader.getColorTextures()) {
                    t.getProperties().setFilterMax(TextureProperties.FILTER_NEAREST);
                    t.getProperties().setFilterMin(TextureProperties.FILTER_NEAREST);
                    skyBox.addTexture(t, -1);
                }
                this.textureLoader.generateAllTextures();
            }
        }
        if (skyBox != null) {
            this.skyScene.addObject(skyBox, false);
        }
    }

    private Object3D generateMapPart(AccesibleMap map, int x, int y) {
        if (map.getTile(x, y).getId() == 4 || map.getTile(x, y).getId() == 5) {
            return this.generateFence(map, x, y);
        } else {
            return generateRestTiles(map, x, y);
        }
    }

    private Object3D generateRestTiles(AccesibleMap map, int x, int y) {
        if (map.getTile(x, y).getId() == 6) {
            boolean leva = x > 0 && (map.getTile(x - 1, y).getId() == 6 || map.getTile(x - 1, y).getId() == 9);
            boolean prava = x < map.getWidth() - 1 && (map.getTile(x + 1, y).getId() == 6 || map.getTile(x + 1, y).getId() == 9);
            boolean predni = y < map.getHeight() - 1 && (map.getTile(x, y + 1).getId() == 6 || map.getTile(x, y + 1).getId() == 9);
            boolean zadni = y > 0 && (map.getTile(x, y - 1).getId() == 6 || map.getTile(x, y - 1).getId() == 9);
            return this.generateModel(map, x, y, leva, prava, predni, zadni);
        } else {
            boolean leva = x > 0 && (map.getTile(x - 1, y).getId() == 9);
            boolean prava = x < map.getWidth() - 1 && (map.getTile(x + 1, y).getId() == 9);
            boolean predni = y < map.getHeight() - 1 && (map.getTile(x, y + 1).getId() == 9);
            boolean zadni = y > 0 && (map.getTile(x, y - 1).getId() == 9);
            return this.generateModel(map, x, y, leva, prava, predni, zadni);
        }

    }

    private Object3D generateFence(AccesibleMap map, int x, int y) {
        boolean leva = x > 0 && (map.getTile(x - 1, y).isSolid());
        boolean prava = x < map.getWidth() - 1 && (map.getTile(x + 1, y).isSolid());
        boolean predni = y < map.getHeight() - 1 && (map.getTile(x, y + 1).isSolid());
        boolean zadni = y > 0 && (map.getTile(x, y - 1).isSolid());
        return this.generateModel(map, x, y, leva, prava, predni, zadni);
    }

    private Object3D generateModel(AccesibleMap map, int x, int y, boolean leva, boolean prava, boolean predni, boolean zadni) {
        int pocetKraju = (leva ? 1 : 0) + (prava ? 1 : 0) + (predni ? 1 : 0) + (zadni ? 1 : 0);

        int S = 0;
        int S1 = 1;
        int S2a = 2;
        int S2b = 3;
        int S3 = 4;
        int S4 = 5;

        Object3D obj = null;
        if (pocetKraju == 0) {
            obj = this.objects[map.getTile(x, y).getId() * 6 + S];
            if (obj == null) {
                obj = this.objects[map.getTile(x, y).getId() * 6 + S];
                if (obj == null) {
                    obj = this.generateDefaultObject();
                } else {
                    obj = obj.copyObjectReference();
                }
            } else {
                obj = obj.copyObjectReference();
            }
            obj.setPosMatrix(new Matrix4f());
            obj.getPosMatrix().translate(new Vector3f(x, 0, y));
            return obj;
        } else if (pocetKraju == 1) {
            obj = this.objects[map.getTile(x, y).getId() * 6 + S1];
            if (obj == null) {
                obj = this.objects[map.getTile(x, y).getId() * 6 + S];
                if (obj == null) {
                    obj = this.generateDefaultObject();
                } else {
                    obj = obj.copyObjectReference();
                }
            } else {
                obj = obj.copyObjectReference();
            }
            obj.setPosMatrix(new Matrix4f());
            obj.getPosMatrix().translate(new Vector3f(x, 0, y));
            if (predni) {
                obj.getPosMatrix().rotate(-(float) (Math.PI / 2), new Vector3f(0, 1, 0));
                obj.getPosMatrix().translate(new Vector3f(0, 0, -1));
            } else if (leva) {
                obj.getPosMatrix().rotate((float) (Math.PI), new Vector3f(0, 1, 0));
                obj.getPosMatrix().translate(new Vector3f(-1, 0, -1));
            } else if (zadni) {
                obj.getPosMatrix().rotate((float) (Math.PI / 2), new Vector3f(0, 1, 0));
                obj.getPosMatrix().translate(new Vector3f(-1, 0, 0));
            }
        } else if (pocetKraju == 2) {
            if ((leva && prava) || (predni && zadni)) {
                obj = this.objects[map.getTile(x, y).getId() * 6 + S2b];
                if (obj == null) {
                    obj = this.objects[map.getTile(x, y).getId() * 6 + S];
                    if (obj == null) {
                        obj = this.generateDefaultObject();
                    } else {
                        obj = obj.copyObjectReference();
                    }
                } else {
                    obj = obj.copyObjectReference();
                }
                obj.setPosMatrix(new Matrix4f());
                obj.getPosMatrix().translate(new Vector3f(x, 0, y));
                if (zadni || predni) {
                    obj.getPosMatrix().rotate((float) (Math.PI / 2), new Vector3f(0, 1, 0));
                    obj.getPosMatrix().translate(new Vector3f(-1, 0, 0));
                }
            } else {
                obj = this.objects[map.getTile(x, y).getId() * 6 + S2a];
                if (obj == null) {
                    obj = this.objects[map.getTile(x, y).getId() * 6 + S];
                    if (obj == null) {
                        obj = this.generateDefaultObject();
                    } else {
                        obj = obj.copyObjectReference();
                    }
                } else {
                    obj = obj.copyObjectReference();
                }
                obj.setPosMatrix(new Matrix4f());
                obj.getPosMatrix().translate(new Vector3f(x, 0, y));
                if (predni && leva) {
                    obj.getPosMatrix().rotate(-(float) (Math.PI / 2), new Vector3f(0, 1, 0));
                    obj.getPosMatrix().translate(new Vector3f(0, 0, -1));
                } else if (leva && zadni) {
                    obj.getPosMatrix().rotate((float) (Math.PI), new Vector3f(0, 1, 0));
                    obj.getPosMatrix().translate(new Vector3f(-1, 0, -1));
                } else if (zadni && prava) {
                    obj.getPosMatrix().rotate((float) (Math.PI / 2), new Vector3f(0, 1, 0));
                    obj.getPosMatrix().translate(new Vector3f(-1, 0, 0));
                }
            }
        } else if (pocetKraju == 3) {
            obj = this.objects[map.getTile(x, y).getId() * 6 + S3];
            if (obj == null) {
                obj = this.objects[map.getTile(x, y).getId() * 6 + S];
                if (obj == null) {
                    obj = this.generateDefaultObject();
                } else {
                    obj = obj.copyObjectReference();
                }
            } else {
                obj = obj.copyObjectReference();
            }
            obj.setPosMatrix(new Matrix4f());
            obj.getPosMatrix().translate(new Vector3f(x, 0, y));
            if (!prava) {
                obj.getPosMatrix().rotate(-(float) (Math.PI / 2), new Vector3f(0, 1, 0));
                obj.getPosMatrix().translate(new Vector3f(0, 0, -1));
            } else if (!predni) {
                obj.getPosMatrix().rotate((float) (Math.PI), new Vector3f(0, 1, 0));
                obj.getPosMatrix().translate(new Vector3f(-1, 0, -1));
            } else if (!leva) {
                obj.getPosMatrix().rotate((float) (Math.PI / 2), new Vector3f(0, 1, 0));
                obj.getPosMatrix().translate(new Vector3f(-1, 0, 0));
            }
        } else if (pocetKraju == 4) {
            obj = this.objects[map.getTile(x, y).getId() * 6 + S4];
            if (obj == null) {
                obj = this.objects[map.getTile(x, y).getId() * 6 + S];
                if (obj == null) {
                    obj = this.generateDefaultObject();
                } else {
                    obj = obj.copyObjectReference();
                }
            } else {
                obj = obj.copyObjectReference();
            }
            obj.setPosMatrix(new Matrix4f());
            obj.getPosMatrix().translate(new Vector3f(x, 0, y));
        }
        return obj;
    }

    private Texture generateDefaultTexture() {
        Texture tex = new Texture();
        BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
        img.setRGB(0, 0, 0xffffffff);
        tex.setData(TextureCreator.getImageData(img));
        TextureProperties tp = new TextureProperties();
        tp.setWidth(1);
        tp.setHeight(1);
        tex.setProperties(tp);
        GraphicsIndex.getGLSynchronizer().generateTexture(tex);
        tex.setTextureCoordsIndex(0);
        return tex;
    }

    private Object3D generateDefaultObject() {
        Object3D obj = new Object3D(this.generateDefaultMesh());
        obj.addTexture(this.generateDefaultTexture(), -1);
        return obj;
    }

    private Mesh generateDefaultMesh() {
        float[] aData = new float[]{0, 0, 0, 0, 1, 0, 0, 0, 0,
            0, 0, 1, 0, 1, 0, 0, 1, 0,
            1, 0, 1, 0, 1, 0, 1, 1, 0,
            1, 0, 0, 0, 1, 0, 1, 0, 0};
        int[] aIndices = new int[]{0, 1, 2, 0, 2, 3};
        FloatBuffer data = BufferUtils.createFloatBuffer(aData.length);
        for (float f : aData) {
            data.put(f);
        }
        data.rewind();
        IntBuffer indices = BufferUtils.createIntBuffer(aIndices.length);
        for (int i : aIndices) {
            indices.put(i);
        }
        indices.rewind();
        return new Mesh(data, indices, 1);
    }

    public void freeCamera(boolean freeCam) {
        this.freeCam = freeCam;
    }

    public AbstractPlayer getPlayerWithCamera() {
        return this.cameraLocation;
    }

    public boolean isCameraFree() {
        return this.freeCam;
    }

    public void locateCameraToPlayer(AbstractPlayer player, float collisionDiameter) {
        super.near = (float) Math.cos(Math.toRadians(super.fov / 2)) * collisionDiameter;
        this.skyScene.near = super.near;
        this.cameraLocation = player;
    }

    public void mouseAction(LWJGLMouseEvent e) {
        if (this.freeCam) {
            this.yaw -= e.getdX();
            this.pitch -= e.getdY();
        }

    }

    public void rotateCamera(float x, float y) {
        if (freeCam) {
            yaw += x;
            pitch += y;
            if (pitch > 90) {
                pitch = 90;
            } else if (pitch < -90) {
                pitch = -90;
            }
        }
    }

    public void keyAction(LWJGLKeyEvent e) {
        if (this.freeCam) {
            float dX = (float) (Math.cos(Math.toRadians(yaw - 90)) * Math.cos(Math.toRadians(pitch)));
            float dZ = (float) -(Math.sin(Math.toRadians(yaw - 90)) * Math.cos(Math.toRadians(pitch)));
            float dY = (float) -Math.sin(Math.toRadians(pitch));
            if (e.getKey() == LWJGLKeyEvent.KEY_W) {
                this.x += dX;
                this.z += dZ;
                this.y += dY;
            } else if (e.getKey() == LWJGLKeyEvent.KEY_S) {
                this.x -= dX;
                this.z -= dZ;
                this.y -= dY;
            }
        }
    }

    public void moveCamera(float by) { //O kolik se má posunout, záporné číslo = dozadu
        if (freeCam) {
            x -= (float) (Math.cos(Math.toRadians(yaw - 180)) * Math.cos(Math.toRadians(pitch)) * by);
            y -= (float) -Math.sin(Math.toRadians(pitch)) * by;
            z -= (float) -(Math.sin(Math.toRadians(yaw - 180)) * Math.cos(Math.toRadians(pitch)) * by);
        }
    }

    private void setCamera() {
        this.camPos.setIdentity();
        this.camPos.rotate((float) Math.toRadians(pitch), new Vector3f(-1, 0, 0));
        this.camPos.rotate((float) Math.toRadians(yaw - 90), new Vector3f(0, -1, 0));
        this.camPos.translate(new Vector3f(-(float) x, -(float) y, -(float) z));
    }

    @Override
    protected void predrawSetup(Scene owner) {
        if (this.mapGenerated == false && this.objectsLoaded == true && map != null) {
            new Thread() {

                @Override
                public void run() {
                    GameScene.this.generateMap(map);
                }
            }.start();
        }
        if (this.freeCam == false && this.cameraLocation != null) {
            this.x = (float) this.cameraLocation.getX();
            this.z = (float) this.cameraLocation.getY();
            this.y = 0.8f;
            this.pitch = (float) this.cameraLocation.getPitch();
            this.yaw = (float) this.cameraLocation.getYaw();
        }
        this.setCamera();

        for (GraphicalPlayer gp : this.players) {
            Matrix4f posMatrix = gp.getObject3D().getPosMatrix();
            posMatrix.setIdentity();
            posMatrix.rotate((float) Math.toRadians(gp.getGraphicsYaw()), new Vector3f(0, 1, 0));
            posMatrix.m30 = (float) gp.getGraphicsX();
            posMatrix.m32 = (float) gp.getGraphicsY();
        }
        for (GraphicalBullet gb : this.bullets) {
            Matrix4f posMatrix = gb.getObject3D().getPosMatrix();
            posMatrix.setIdentity();
            posMatrix.rotate((float) Math.toRadians(gb.getGraphicsYaw()), new Vector3f(0, 1, 0));
            posMatrix.m30 = (float) gb.getGraphicsX();
            posMatrix.m32 = (float) gb.getGraphicsY();
            posMatrix.m31 = (float) gb.getGraphicsZ();
        }
    }

    public double getPitch() {
        return this.pitch;
    }

    public double getYaw() {
        return this.yaw;
    }

    private Object3D generateModel(File f) {
        TMDLoader meshLoader = new TMDLoader();
        TMMLoader texLoader = new TMMLoader();
        meshLoader.loadFile(f);
        Mesh mesh = new Mesh(meshLoader.getMeshData(), meshLoader.getIndices(), meshLoader.getTexCoordsNumber());
        texLoader.loadFile(meshLoader.getMaterialInfo());
        texLoader.generateAllTextures();

        Object3D obj = new Object3D(mesh);
        if (texLoader.getColorTextures().length > 0) {
            obj.addTexture(texLoader.getColorTextures()[0], -1);
        }

        GraphicsIndex.getGLSynchronizer().generateVBO(obj);
        return obj;
    }

    public void addPlayer(GraphicalPlayer player) {
        this.players.add(player);
        if (player.getObject3D() == null) {
            player.setObject3D(this.generateModel(new File("resources/models/players/player.tmd")));
        }
        this.addObject(player.getObject3D(), false);
    }

    public void removePlayer(GraphicalPlayer player) {
        this.players.remove(player);
        this.removeObject(player.getObject3D());
    }

    public ArrayList<GraphicalPlayer> getPlayers() {
        return this.players;
    }

    public void addBullet(GraphicalBullet b) {
        this.bullets.add(b);
        if (b.getObject3D() == null) {
            b.setObject3D(this.generateModel(new File("resources/models/bullets/bullet.tmd")));
        }
        this.addObject(b.getObject3D(), false);
    }

    public void removeBullet(GraphicalBullet b) {
        this.bullets.remove(b);
        this.removeObject(b.getObject3D());
    }
}
